﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace Borg.Framework.CompositionBinding
{
    public class CreateAndPlugBehavior : ICompositionBehavior<PlugTypeInfo>
    {
        private readonly List<PlugTypeInfo> _items;
        private SlotInfo _slot;

        public event EventHandler<ItemEventArgs<PlugTypeInfo>> ItemAdded;
        public event EventHandler<ItemEventArgs<PlugTypeInfo>> ItemRemoved;

        public event EventHandler<ItemEventArgs<PlugTypeInfo>> ItemComposed;
        public event EventHandler<ItemEventArgs<PlugTypeInfo>> ItemDecomposed;

        public CreateAndPlugBehavior()
        {
            _items = new List<PlugTypeInfo>();
        }

        public CreateAndPlugBehavior(SlotInfo slot)
        {
            _items = new List<PlugTypeInfo>();
            Slot = slot;
        }

        private void Add(PlugTypeInfo item)
        {
            _items.Add(item);
            OnEvent(ItemAdded, new ItemEventArgs<PlugTypeInfo>(item));
        }

        public void Compose(PlugTypeInfo item)
        {
            if (_slot == null)
                return;

            if (_slot.PluggedPlugInfos.Any(pi => pi.PlugTypeInfo == item))
                return;

            ExtensionInfo ei =
                _slot.Unique
                    ? item.ExtensionTypeInfo.CreateUniqueExtension()
                    : item.ExtensionTypeInfo.GetSharedExtension(true);

            _slot.Plug(ei.PlugInfos[item.Name]);
        }

        public void Decompose(PlugTypeInfo item)
        {
            if (_slot == null)
                return;

            for (var i = _slot.PluggedPlugInfos.Count - 1; i >= 0; i--)
                if (_slot.PluggedPlugInfos[i].PlugTypeInfo == item)
                    _slot.Unplug(_slot.PluggedPlugInfos[i]);
        }

        private void OnDeregistered(object sender, RegisterEventArgs args)
        {
            Remove(args.PlugTypeInfo);
        }

        private void OnEvent(EventHandler<ItemEventArgs<PlugTypeInfo>> handler, ItemEventArgs<PlugTypeInfo> args)
        {
            if (handler != null)
                handler(this, args);
        }

        private void OnPlugged(object sender, PlugEventArgs args)
        {
            OnEvent(ItemComposed, new ItemEventArgs<PlugTypeInfo>(args.PlugInfo.PlugTypeInfo));
        }

        private void OnRegistered(object sender, RegisterEventArgs args)
        {
            Add(args.PlugTypeInfo);
        }

        private void OnUnplugged(object sender, PlugEventArgs args)
        {
            OnEvent(ItemDecomposed, new ItemEventArgs<PlugTypeInfo>(args.PlugInfo.PlugTypeInfo));
        }

        private void Remove(PlugTypeInfo item)
        {
            _items.Remove(item);
            OnEvent(ItemRemoved, new ItemEventArgs<PlugTypeInfo>(item));
        }

        public IEnumerable<PlugTypeInfo> ComposedItems
        {
            get
            {
                var list = new List<PlugTypeInfo>();

                if (_slot != null)
                {
                    foreach (var pi in _slot.PluggedPlugInfos.Where(pi => !list.Contains(pi.PlugTypeInfo)))
                        list.Add(pi.PlugTypeInfo);

                    return list;
                }

                return list;
            }
        }

        public IEnumerable<PlugTypeInfo> Items
        {
            get { return _items; }
        }

        public SlotInfo Slot
        {
            get { return _slot; }
            set
            {
                if (_slot == value)
                    return;

                if (_slot != null)
                {
                    _slot.Registered -= OnRegistered;
                    _slot.Deregistered -= OnDeregistered;
                    _slot.Plugged -= OnPlugged;
                    _slot.Unplugged -= OnUnplugged;

                    foreach (var pi in _slot.PluggedPlugInfos)
                        OnEvent(ItemDecomposed, new ItemEventArgs<PlugTypeInfo>(pi.PlugTypeInfo));

                    foreach (var pti in _slot.RegisteredPlugTypeInfos)
                        Remove(pti);
                }

                _slot = value;

                if (_slot == null)
                    return;
                
                _slot.AutoPlug = false;

                if (!_slot.IsOpen)
                    _slot.Open();

                _slot.Registered += OnRegistered;
                _slot.Deregistered += OnDeregistered;
                _slot.Plugged += OnPlugged;
                _slot.Unplugged += OnUnplugged;

                foreach (var pti in _slot.RegisteredPlugTypeInfos)
                    Add(pti);

                foreach (var pi in _slot.PluggedPlugInfos)
                    OnEvent(ItemComposed, new ItemEventArgs<PlugTypeInfo>(pi.PlugTypeInfo));
            }
        }
    }
}

